/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package axisandallies;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import utilities.Camera;
import utilities.EulerCamera;

import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL13.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;
import utilities.BoardCamera;
import utilities.Util;

/**
 * @author ctuchsch
 */
public class Game
{

    //<editor-fold desc="Constants Variables"> 
    public static final int WIDTH = 1920;
    public static final int HEIGHT = 1080;
    private final String TITLE = "Axis and Allies Pre-Alpha 0.01";
    private final int MAX_FRAME_RATE = 60;
    private final int floatByteSize = 4;
    private final int positionFloatCount = 4;
    private final int colorFloatCount = 4;
    private final int textureFloatCount = 2;
    private final int floatsPerVertex = positionFloatCount + colorFloatCount + textureFloatCount;
    private final int vertexFloatSizeInBytes = floatByteSize * floatsPerVertex;
    //</editor-fold>
    //<editor-fold desc="Constants Variables">   
    private Camera camera;
    private Board board;
    private Menu menu;
    private int vaoHandle;
    private int vaoMenuHandle;
    private int textureHandle;
    private int vboInterleavedHandle;
    private int vboMenuHandle;
    private FloatBuffer menuData;
    private ByteBuffer menuIndices;
    private int vboMenuIndicesHandle;
    private int vboIndicesHandle;
    private int vertShaderHandle;
    private int fragShaderHandle;
    private int programHandle;
    private ByteBuffer indicesData;
    private FloatBuffer interleavedData;
    private int indicesCount;
    private int projectionMatrixLocation = 0;
    private int viewMatrixLocation = 0;
    private int modelMatrixLocation = 0;
    private boolean renderMenu = false;
    private FloatBuffer identityMatrix;

    //</editor-fold>
    /**
     * @param args the command line arguments
     */
    public Game()
    {
        run();
    }

    private void setUpTexture()
    {
        try {
            textureHandle = Util.loadTexture("res/Axis_and_Allies.png");
        } catch (IOException ex) {
            CleanAndClose();
        }

    }

    private void setUpGame()
    {
        setUpDisplay();
        setUpShaders();
        setUpTexture();
        setUpBoard();
        setUpCamera();
    }

    private void run()
    {
        //<editor-fold desc="Setup Code">
        setUpGame();
        //</editor-fold>

        while (!Display.isCloseRequested()) {
            update();
            render();
            Display.update();
            Display.sync(MAX_FRAME_RATE);
        }
        CleanAndClose();
    }

    public static void main(String[] args)
    {
        new Game();
    }

    private void setUpDisplay()
    {
        PixelFormat pixelFormat = new PixelFormat();
        ContextAttribs contextAttributes = new ContextAttribs(3, 2)
                .withForwardCompatible(true)
                .withProfileCore(true);

        try {
            Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
            Display.setVSyncEnabled(true);
            Display.setTitle(TITLE);
            Display.create(pixelFormat, contextAttributes);
        } catch (LWJGLException e) {
            System.err.println("Display failed to initialize");
            CleanAndClose();
        }
        //glEnable(GL_DEPTH_TEST);
        glEnable(GL_CULL_FACE);
        glViewport(0, 0, WIDTH, HEIGHT);
        this.exitOnGLError("setUpDisplay");
    }

    private void setUpCamera()
    {
        camera = new EulerCamera.Builder()
                .setAspectRatio((float) Display.getWidth() / Display.getHeight())
                .setRotation(60f, 0f, 0f)
                .setPosition(0f, 1f, .1f)
               // .setIntervals(.05f, 5f, 55, 85, 20, 10)
                .setFieldOfView(60)
               // .setMaxPanPositions(10, 5f)
                .build();
        Mouse.setGrabbed(true);
    }

    private void render()
    {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(programHandle);

        if (renderMenu) {
            glEnable(GL_DEPTH_TEST);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, textureHandle);
            glBindVertexArray(vaoMenuHandle);
            glEnableVertexAttribArray(0);
            glEnableVertexAttribArray(1);
            glEnableVertexAttribArray(2);

            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboMenuIndicesHandle);

            //update matrix in shaders
            glUniformMatrix4(projectionMatrixLocation, false, camera.getProjectionMatrix());
            glUniformMatrix4(viewMatrixLocation, false, identityMatrix);
            glUniformMatrix4(modelMatrixLocation, false, identityMatrix);

            glDrawElements(GL_TRIANGLES, menu.getIndicesCount(), GL_UNSIGNED_BYTE, 0);

            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
            glDisableVertexAttribArray(0);
            glDisableVertexAttribArray(1);
            glDisableVertexAttribArray(2);
            glBindVertexArray(0);
        }
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, textureHandle);

        glBindVertexArray(vaoHandle);
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glEnableVertexAttribArray(2);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndicesHandle);

        //update matrix in shaders
        glUniformMatrix4(projectionMatrixLocation, false, camera.getProjectionMatrix());
        glUniformMatrix4(viewMatrixLocation, false, camera.getViewMatrix());
        glUniformMatrix4(modelMatrixLocation, false, board.getModelMatrix());

        glDrawElements(GL_TRIANGLES, indicesCount, GL_UNSIGNED_BYTE, 0);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        glDisableVertexAttribArray(2);
        glBindVertexArray(0);
        glUseProgram(0);
        this.exitOnGLError("render");

    }

    private void update()
    {
        //check input apply camera position changes
        checkInput();
        camera.applyTranslations();
        board.applyTranslations();
    }

    private void checkInput()
    {
        if (!renderMenu) {
            camera.processMouse(1, 90, -90);
            camera.processKeyboard(16, 1, 1, 1);
        }
        while (Keyboard.next()) {
            if (Keyboard.getEventKey() == Keyboard.KEY_ESCAPE && Keyboard.getEventKeyState()) {
                setUpMenu();
                renderMenu = true;
            }
            if (Keyboard.getEventKey() == Keyboard.KEY_C && Keyboard.getEventKeyState())
                //cleanUpMenu();
                renderMenu = false;
        }
    }

    private void setUpMenu()
    {
        menu = new Menu();
        menuData = BufferUtils.createFloatBuffer(menu.numVertex());
        menuData.put(menu.toFloatBuffer());
        menuData.flip();
        menuIndices = BufferUtils.createByteBuffer(menu.getIndicesCount());
        menuIndices.put(menu.getIndices());
        menuIndices.flip();

        vaoMenuHandle = glGenVertexArrays();
        glBindVertexArray(vaoMenuHandle);

        vboMenuHandle = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vboMenuHandle);
        glBufferData(GL_ARRAY_BUFFER, menuData, GL_STATIC_DRAW);
        glVertexAttribPointer(0, positionFloatCount, GL_FLOAT, false, vertexFloatSizeInBytes, 0);
        glVertexAttribPointer(1, colorFloatCount, GL_FLOAT, false, vertexFloatSizeInBytes, floatByteSize * positionFloatCount);
        glVertexAttribPointer(2, textureFloatCount, GL_FLOAT, false, vertexFloatSizeInBytes, (floatByteSize * positionFloatCount) + (floatByteSize * colorFloatCount));

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);

        vboMenuIndicesHandle = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboMenuIndicesHandle);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, menuIndices, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        identityMatrix = BufferUtils.createFloatBuffer(16);
        identityMatrix.put(new float[]{1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1});
        identityMatrix.flip();

    }

    private void setUpBoard()
    {
        board = new Board();
        interleavedData = BufferUtils.createFloatBuffer(board.numVertex());
        interleavedData.put(board.toFloatBuffer());
        interleavedData.flip();
        indicesCount = board.getIndicesCount();
        indicesData = BufferUtils.createByteBuffer(indicesCount);
        indicesData.put(board.getIndices());
        indicesData.flip();

        vaoHandle = glGenVertexArrays();
        glBindVertexArray(vaoHandle);

        vboInterleavedHandle = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vboInterleavedHandle);
        glBufferData(GL_ARRAY_BUFFER, interleavedData, GL_STREAM_DRAW);
        glVertexAttribPointer(0, positionFloatCount, GL_FLOAT, false, vertexFloatSizeInBytes, 0);
        glVertexAttribPointer(1, colorFloatCount, GL_FLOAT, false, vertexFloatSizeInBytes, floatByteSize * positionFloatCount);
        glVertexAttribPointer(2, textureFloatCount, GL_FLOAT, false, vertexFloatSizeInBytes, (floatByteSize * positionFloatCount) + (floatByteSize * colorFloatCount));

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);

        vboIndicesHandle = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndicesHandle);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesData, GL_STREAM_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        this.exitOnGLError("setUpBoard");
    }

    private void setUpShaders()
    {
        try {
            vertShaderHandle = Util
                    .loadShader("src/shaders/vertShader.glsl", GL_VERTEX_SHADER);
            fragShaderHandle = Util
                    .loadShader("src/shaders/fragShader.glsl", GL_FRAGMENT_SHADER);
        } catch (IOException ex) {
            ex.printStackTrace();
            CleanAndClose();
        }

        programHandle = glCreateProgram();
        Util.attachShaders(programHandle, vertShaderHandle, fragShaderHandle);

        glBindAttribLocation(programHandle, 0, "in_Position");
        glBindAttribLocation(programHandle, 1, "in_Color");
        glBindAttribLocation(programHandle, 2, "in_TextureCoord");

        glLinkProgram(programHandle);

        projectionMatrixLocation = glGetUniformLocation(programHandle, "projectionMatrix");
        viewMatrixLocation = glGetUniformLocation(programHandle, "viewMatrix");
        modelMatrixLocation = glGetUniformLocation(programHandle, "modelMatrix");

        //Always do this after setting the logcation or it defaults them :(        

        glValidateProgram(programHandle);

        this.exitOnGLError("loadShaders");
    }

    private void CleanAndClose()
    {
        glDeleteTextures(textureHandle);
        glUseProgram(0);
        glDetachShader(programHandle, vertShaderHandle);
        glDetachShader(programHandle, fragShaderHandle);

        glDeleteShader(vertShaderHandle);
        glDeleteShader(fragShaderHandle);
        glDeleteProgram(programHandle);

        glBindVertexArray(vaoHandle);

        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        glDisableVertexAttribArray(2);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glDeleteBuffers(vboIndicesHandle);

        glBindVertexArray(0);
        glDeleteVertexArrays(vaoHandle);

        Display.destroy();
        System.exit(1);
    }

    private void exitOnGLError(String errorMessage)
    {
        int errorValue = glGetError();

        if (errorValue != GL_NO_ERROR) {
            String errorString = GLU.gluErrorString(errorValue);
            System.err.println("ERROR - " + errorMessage + ": " + errorString);

            if (Display.isCreated())
                Display.destroy();
            System.exit(-1);
        }
    }

    private void cleanUpMenu()
    {
        glBindVertexArray(vaoMenuHandle);

        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        glDisableVertexAttribArray(2);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glDeleteBuffers(vboMenuIndicesHandle);

        glBindVertexArray(0);
        glDeleteVertexArrays(vaoHandle);
        menu = null;
        vaoMenuHandle = 0;
        vboMenuIndicesHandle = 0;
        menuData = null;
        menuIndices = null;
    }
}
